/*
 * ServerConfiguration.java
 *
 * Created on June 12, 2007, 1:11 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.atomojo.app.admin.AdminXML;
import org.atomojo.app.auth.AuthException;
import org.atomojo.app.auth.AuthService;
import org.atomojo.app.client.Link;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.restlet.data.MediaType;

/**
 *
 * @author alex
 */
public class ServerConfiguration
{

   public static class Interface {
      String address;
      boolean secure;
      int port;
      
      public Interface(String addr,int port,boolean secure)
      {
         this.address = addr;
         this.port = port;
         this.secure = secure;
      }
      
      public int getPort()
      {
         return port;
      }
      
      public void setPort(int port)
      {
         this.port = port;
      }

      public String getAddress()
      {
         return address;
      }

      public void setAddress(String address)
      {
         this.address = address;
      }

      public boolean isSecure()
      {
         return secure;
      }

      public void setSecure(boolean secure)
      {
         this.secure = secure;
      }
      
   }
   public static class Host {
      String dbName;
      String name;
      String addr;
      int port;
      boolean allowQueries;
      boolean editClient;
      File hostLog;
      ServerConfiguration owner;
      
      public Host(ServerConfiguration owner,String dbName,String name) {
         this.dbName = dbName;
         this.name = name;
         this.addr = null;
         this.port = -1;
         this.allowQueries = false;
         this.editClient = false;
      }
      public Host(ServerConfiguration owner,String dbName,String name,String addr,int port,boolean allowQueries,boolean editClient,File hostLog) {
         this.dbName = dbName;
         this.name = name;
         this.addr = addr;
         this.port = port;
         this.allowQueries = allowQueries;
         this.editClient = editClient;
         this.hostLog = hostLog;
      }
      
      public ServerConfiguration getConfiguration() {
         return owner;
      }
      
      public boolean allowQueries() {
         return allowQueries;
      }
      
      public boolean allowEditClient() {
         return editClient;
      }

      public String getDatabaseName() {
         return dbName;
      }
      public String getName() {
         return name;
      }
      public String getAddress() {
         return addr;
      }
      public int getPort() {
         return port;
      }
      public File getLogFile() {
         return hostLog;
      }
   }
   
   public static class AdminHost extends Host {
      String authName;
      public AdminHost(ServerConfiguration owner,String name,String addr,int port,String authName,File logDir) {
         super(owner,null,name,addr,port,false,false,logDir);
         this.authName = authName;
      }
      
      public String getAuthName() {
         return authName;
      }
      
   }

   public static class ResourceHost extends Host {
      Set<String> databases;
      public ResourceHost(ServerConfiguration owner,String name,String addr,int port,boolean queries,boolean editClient,File logDir) {
         super(owner,null,name,addr,port,queries,editClient,logDir);
         this.databases = new TreeSet<String>();
      }

      public Set<String> getDatabases() {
         return databases;
      }

   }

   
   public static class Auth {
      String name;
      Class<? extends AuthService> serviceClass;
      Properties props;
      public Auth(String name,Class<? extends AuthService> serviceClass,Properties props) {
         this.name = name;
         this.serviceClass = serviceClass;
         this.props = props;
      }
      
      public String getName() {
         return name;
      }
      
      public Class getServiceClass() {
         return serviceClass;
      }
      
      public Properties getProperties() {
         return props;
      }
      
      public AuthService newInstance() 
         throws AuthException
      {
         try {
            AuthService service = serviceClass.newInstance();
            service.init(props);
            return service;
         } catch (InstantiationException ex) {
            throw new AuthException("Cannot instantiate service "+serviceClass.getName(),ex);
         } catch (IllegalAccessException ex) {
            throw new AuthException("Cannot instantiate service "+serviceClass.getName(),ex);
         }
      }
   }
   
   public static class Database {
      String name;
      String auth;
      String group;
      String groupAlias;
      
      public Database(String name,String auth)
      {
         this.name = name;
         this.auth = auth;
         this.group = null;
         this.groupAlias = null;
      }
      
      public Database(String name,String auth,String group,String groupAlias)
      {
         this.name = name;
         this.auth = auth;
         this.group = group;
         this.groupAlias = groupAlias;
      }

      public String getName() {
         return name;
      }
      
      public String getAuthName() {
         return auth;
      }

      public String getGroup() {
         return group;
      }

      public String getGroupAlias() {
         return groupAlias;
      }

   }
   
   long autoconfCheck;
   String storageClassName;
   Map<String,AdminHost> admins;
   Map<String,ResourceHost> resources;
   Map<String,Host> hosts;
   Map<String,Database> databases;
   Map<String,Auth> authServices;
   List<Interface> interfaces;
   File keystoreFile;
   File logDir;
   String keystorePassword;
   String keyPassword;
   List<Link> autoconfs;
   File tmpDir;
   
   /** Creates a new instance of ServerConfiguration */
   public ServerConfiguration()
   {
      autoconfCheck = 180*1000;
      autoconfs = new ArrayList<Link>();
      hosts = new TreeMap<String,Host>();
      admins = new TreeMap<String,AdminHost>();
      resources = new TreeMap<String,ResourceHost>();
      authServices = new TreeMap<String,Auth>();
      databases = new TreeMap<String,Database>();
      interfaces = new ArrayList<Interface>();
      tmpDir = new File(".");
   }
   
   public String getStorageClassName() {
      return storageClassName;
   }
   
   public void setStorageClassName(String name)
   {
      storageClassName = name;
   }
   
   public long getAutoConfigurationCheckWait() {
      return autoconfCheck;
   }
   
   public void setAutoConfigurationCheckWait(long value) {
      autoconfCheck = value;
   }
   
   public List<Link> getAutoConfiguration() {
      return autoconfs;
   }
   
   public File getKeystoreFile() {
      return keystoreFile;
   }
   
   public void setKeystoreFile(File f)
   {
      keystoreFile = f;
   }
   
   public void setLogDirectory(File dir) {
      logDir = dir;
   }
   
   public File getLogDirectory() {
      return logDir;
   }
   
   public String getKeystorePassword() {
      return keystorePassword;
   }
   
   public void setKeystorePassword(String password)
   {
      keystorePassword = password;
   }
   
   public String getKeyPassword() {
      return keyPassword;
   }
   
   public void setKeyPassword(String password)
   {
      keyPassword = password;
   }
   
   public File getTempDirectory() {
      return tmpDir;
   }
      
   public void setTempDirectory(File dir) {
      tmpDir = dir;
   }
      
   public void load(URI location)
      throws IOException,XMLException
   {
      DocumentLoader loader = new SAXDocumentLoader();
      Document doc = loader.load(location);
      Element top = doc.getDocumentElement();
      if (!top.getName().equals(AdminXML.NM_SERVER)) {
         throw new XMLException("Expecting "+AdminXML.NM_SERVER+" but found "+top.getName());
      }
      String value = top.getAttributeValue("autoconf-check");
      if (value!=null) {
         autoconfCheck = Long.parseLong(value)*1000;
      }
      
      storageClassName = top.getAttributeValue("storage-class");
      
      value = top.getAttributeValue("logs");
      if (location.getScheme().equals("file")) {
         File locFile = new File(location.getSchemeSpecificPart());
         File test = new File(locFile,value==null ? "logs" : value);
         if (test.exists()) {
            logDir = test;
         } else {
            logDir = new File(value==null ? "logs" : value);
         }
      }
      value = top.getAttributeValue("temp.dir");
      if (location.getScheme().equals("file")) {
         File locFile = new File(location.getSchemeSpecificPart());
         tmpDir = value==null ? locFile.getParentFile() : new File(locFile,value);
      } else {
         tmpDir = new File(".");
      }
      Iterator<Element> autoconfElements = top.getElementsByName(AdminXML.NM_AUTOCONF);
      while (autoconfElements.hasNext()) {
         Element autoconfE = autoconfElements.next();
         String href = autoconfE.getAttributeValue("href");
         if (href!=null) {
            Link l = new Link("autoconf",MediaType.APPLICATION_ATOM_XML,autoconfE.getBaseURI().resolve(href));
            l.setIdentity(autoconfE.getAttributeValue("username"),autoconfE.getAttributeValue("password"));
            autoconfs.add(l);
         }
      }
      Iterator<Element> interfaceElements = top.getElementsByName(AdminXML.NM_INTERFACE);
      while (interfaceElements.hasNext()) {
         Element interfaceE = interfaceElements.next();
         String addr = interfaceE.getAttributeValue("address");
         String portS = interfaceE.getAttributeValue("port");
         boolean secure = interfaceE.getAttributeValue("secure")==null || "true".equals(interfaceE.getAttributeValue("secure"));
         int port = portS==null ? (secure ? 443 : 80) : Integer.parseInt(portS);
         interfaces.add(new Interface(addr,port,secure));
      }
      
      Iterator<Element> hostElements = top.getElementsByName(AdminXML.NM_HOST);
      while (hostElements.hasNext()) {
         Host host = createHost(hostElements.next());
         String key = host.getName();
         if (host.getPort()<0) {
            key = key+":*";
         } else {
            key = key+":"+host.getPort();
         }
         hosts.put(key,host);
      }
      Iterator<Element> adminElements = top.getElementsByName(AdminXML.NM_ADMIN);
      while (adminElements.hasNext()) {
         AdminHost host = createAdminHost(adminElements.next());
         admins.put(host.getName(),host);
      }

      Iterator<Element> resourceElements = top.getElementsByName(AdminXML.NM_RESOURCE);
      while (resourceElements.hasNext()) {
         Element spec = resourceElements.next();
         String type = spec.getAttributeValue("type");
         if (type==null || type.equals("database")) {
            ResourceHost host = createResourceHost(spec);
            resources.put(host.getName(),host);
         } else {
            throw new XMLException("Unrecognized resource host type "+type);
         }
      }

      Iterator<Element> databaseElements = top.getElementsByName(AdminXML.NM_DATABASE);
      while (databaseElements.hasNext()) {
         Element databaseE = databaseElements.next();
         String dbName = databaseE.getAttributeValue("name");
         String auth = databaseE.getAttributeValue("auth");
         String group = databaseE.getAttributeValue("group");
         String alias = databaseE.getAttributeValue("alias");
         Database database = new Database(dbName,auth,group,alias);
         databases.put(dbName,database);
      }
      Iterator<Element> authElements = top.getElementsByName(AdminXML.NM_AUTH);
      while (authElements.hasNext()) {
         Element authE = authElements.next();
         try {
            Auth auth = createAuth(authE);
            authServices.put(auth.getName(),auth);
         } catch (ClassNotFoundException ex) {
            throw new XMLException("Cannot load class "+authE.getAttributeValue("class")+" for auth service "+authE.getAttributeValue("name"),ex);
         }
      }
      Element keystoreE = top.getFirstElementNamed(AdminXML.NM_KEYSTORE);
      String href = keystoreE.getAttributeValue("href");
      URI keystoreRef = keystoreE.getBaseURI().resolve(href);
      keystoreFile = new File(keystoreRef.getSchemeSpecificPart());
      keystorePassword = keystoreE.getAttributeValue("password");
      keyPassword = keystorePassword;
      String altPassword = keystoreE.getAttributeValue("key-password");
      if (altPassword!=null) {
         keyPassword = altPassword;
      }
   }
   
   public Auth createAuth(Element authE)
      throws ClassNotFoundException
   {
      String name = authE.getAttributeValue("name");
      String className = authE.getAttributeValue("class");
      Class<?> qClass = this.getClass().forName(className);
      Class<? extends AuthService> authClass = qClass.asSubclass(AuthService.class);
      Properties props = new Properties();
      for (Name attName : authE.getAttributes().keySet()) {
         props.put(attName.getLocalName(),authE.getAttributeValue(attName));
      }
      props.put("base-uri",authE.getBaseURI().toString());
      Auth auth = new Auth(name,authClass,props);
      return auth;
   }
   
   public Host createHost(Element hostE)
   {
      String dbName = hostE.getAttributeValue("database");
      String name = hostE.getAttributeValue("name");
      String addr = hostE.getAttributeValue("address");
      String port = hostE.getAttributeValue("port");
      String log = hostE.getAttributeValue("log");
      File hostLog = null;
      if (log!=null) {
         File test = new File(logDir,log);
         if (test.exists()) {
            if (test.isDirectory()) {
               hostLog = new File(test,"*".equals(name) ? "any.log" : name+".log");
            } else {
               hostLog = test;
            }
         } else {
            hostLog = test;
         }
      } else {
         hostLog = new File(logDir,"*".equals(name) ? "any.log" : name+".log");
      }
      boolean queries = "true".equals(hostE.getAttributeValue("allow-queries"));
      boolean editClient = "true".equals(hostE.getAttributeValue("edit-client"));
      Host host = new Host(this,dbName,name,addr,port==null ? -1 : Integer.parseInt(port),queries,editClient,hostLog);
      return host;
   }
   
   public AdminHost createAdminHost(Element hostE)
   {
      String authName = hostE.getAttributeValue("auth");
      String name = hostE.getAttributeValue("name");
      String addr = hostE.getAttributeValue("address");
      String port = hostE.getAttributeValue("port");
      String log = hostE.getAttributeValue("log");
      File hostLog = null;
      if (log!=null) {
         File test = new File(logDir,log);
         if (test.exists()) {
            if (test.isDirectory()) {
               hostLog = new File(test,name+".log");
            } else {
               hostLog = test;
            }
         } else {
            hostLog = test;
         }
      } else {
         hostLog = new File(logDir,name+".log");
      }
      AdminHost host = new AdminHost(this,name,addr,port==null ? -1 : Integer.parseInt(port),authName,hostLog);
      return host;
   }

   public ResourceHost createResourceHost(Element hostE)
   {
      String name = hostE.getAttributeValue("name");
      String addr = hostE.getAttributeValue("address");
      String port = hostE.getAttributeValue("port");
      String log = hostE.getAttributeValue("log");
      File hostLog = null;
      if (log!=null) {
         File test = new File(logDir,log);
         if (test.exists()) {
            if (test.isDirectory()) {
               hostLog = new File(test,name+".log");
            } else {
               hostLog = test;
            }
         } else {
            hostLog = test;
         }
      } else {
         hostLog = new File(logDir,name+".log");
      }
      boolean queries = "true".equals(hostE.getAttributeValue("allow-queries"));
      boolean editClient = "true".equals(hostE.getAttributeValue("edit-client"));
      ResourceHost host = new ResourceHost(this,name,addr,port==null ? -1 : Integer.parseInt(port),queries,editClient,hostLog);
      Iterator<Element> databases = hostE.getElementsByName(AdminXML.NM_DATABASE);
      while (databases.hasNext()) {
         String dname = databases.next().getAttributeValue("name");
         if (dname!=null) {
            dname = dname.trim();
            if (dname.length()>0) {
               host.getDatabases().add(dname);
            }
         }
      }
      return host;
   }

   
   public Map<String,Host> getHosts() {
      return hosts;
   }
   
   public Map<String,AdminHost> getAdminHosts() {
      return admins;
   }
   
   public Map<String,ResourceHost> getResourceHosts() {
      return resources;
   }

   public List<Interface> getInterfaces() {
      return interfaces;
   }
   
   public Map<String,Auth> getAuthServices() {
      return authServices;
   }
   
   public Map<String,Database> getDatabases() {
      return databases;
   }
   
   public void store(URI baseURI,ItemDestination dest)
      throws XMLException
   {
      String baseURIValue = baseURI.toString();
      URI baseDir = URI.create(baseURIValue.substring(0,baseURIValue.lastIndexOf('/')+1));
      ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
      dest.send(constructor.createDocument(baseURI));
      Element top = constructor.createElement(AdminXML.NM_SERVER);
      if (autoconfCheck!=((long)(180*1000))) {
         top.setAttributeValue("autoconf-check",Long.toString(autoconfCheck/1000));
      }
      dest.send(top);
      dest.send(constructor.createCharacters("\n"));
      
      Element keystoreE = constructor.createElement(AdminXML.NM_KEYSTORE);
      keystoreE.setAttributeValue("href",baseDir.relativize(keystoreFile.toURI()).toString());
      keystoreE.setAttributeValue("password",keystorePassword);
      if (!keystorePassword.equals(keyPassword)) {
         keystoreE.setAttributeValue("key-password",keyPassword);
      }
      dest.send(keystoreE);
      dest.send(constructor.createElementEnd(AdminXML.NM_KEYSTORE));
      dest.send(constructor.createCharacters("\n"));
      
      for (Auth auth : authServices.values()) {
         Element authE = constructor.createElement(AdminXML.NM_AUTH);
         for (Object nameO : auth.getProperties().keySet()) {
            String name = nameO.toString();
            authE.setAttributeValue(name,auth.getProperties().getProperty(name));
         }
         dest.send(authE);
         dest.send(constructor.createElementEnd(AdminXML.NM_AUTH));
         dest.send(constructor.createCharacters("\n"));
      }
      
      for (Link link : autoconfs) {
         Element autoconfE = constructor.createElement(AdminXML.NM_AUTOCONF);
         autoconfE.setAttributeValue("href",baseURI.relativize(link.getLink()).toString());
         if (link.getUsername()!=null) {
            autoconfE.setAttributeValue("username",link.getUsername());
            autoconfE.setAttributeValue("password",link.getPassword());
         }
         dest.send(autoconfE);
         dest.send(constructor.createElementEnd(AdminXML.NM_AUTOCONF));
         dest.send(constructor.createCharacters("\n"));
      }
      
      for (Database database : databases.values()) {
         Element databaseE = constructor.createElement(AdminXML.NM_DATABASE);
         databaseE.setAttributeValue("name",database.getName());
         databaseE.setAttributeValue("auth",database.getAuthName());
         dest.send(databaseE);
         dest.send(constructor.createElementEnd(AdminXML.NM_DATABASE));
         dest.send(constructor.createCharacters("\n"));
      }
      for (Interface iface : interfaces) {

         Element ifaceE = constructor.createElement(AdminXML.NM_INTERFACE);
         ifaceE.setAttributeValue("address",iface.getAddress());
         ifaceE.setAttributeValue("port",Integer.toString(iface.getPort()));
         ifaceE.setAttributeValue("secure",iface.isSecure() ? "true" : "false");
         dest.send(ifaceE);
         dest.send(constructor.createElementEnd(AdminXML.NM_INTERFACE));
         dest.send(constructor.createCharacters("\n"));
      }

      for (AdminHost host : admins.values()) {
         
         Element hostE = constructor.createElement(AdminXML.NM_ADMIN);
         hostE.setAttributeValue("name",host.getName());
         if (host.getAddress()!=null) {
            hostE.setAttributeValue("address",host.getAddress());
         }
         if (host.getPort()>0) {
            hostE.setAttributeValue("port",Integer.toString(host.getPort()));
         }
         if (host.getAuthName()!=null) {
            hostE.setAttributeValue("auth",host.getAuthName());
         }
         dest.send(hostE);
         dest.send(constructor.createElementEnd(AdminXML.NM_ADMIN));
         dest.send(constructor.createCharacters("\n"));
      }
      
      for (Host host : hosts.values()) {
         
         Element hostE = constructor.createElement(AdminXML.NM_HOST);
         hostE.setAttributeValue("database",host.getDatabaseName());
         hostE.setAttributeValue("name",host.getName());
         if (host.getAddress()!=null) {
            hostE.setAttributeValue("address",host.getAddress());
         }
         if (host.getPort()>0) {
            hostE.setAttributeValue("port",Integer.toString(host.getPort()));
         }
         dest.send(hostE);
         dest.send(constructor.createElementEnd(AdminXML.NM_HOST));
         dest.send(constructor.createCharacters("\n"));
      }
      
      dest.send(constructor.createElementEnd(AdminXML.NM_SERVER));
      dest.send(constructor.createDocumentEnd());
   }
   
}
